﻿using DG.Tweening;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.UI;
using static UnityEditor.Progress;

public class GameManager : MonoBehaviour
{
    //游戏状态，开始游戏和停止游戏
    enum GameState
    { 
        Start,
        Stop,
    }
    //初始为停止游戏
    GameState gameState = GameState.Stop;
    public Button StartBtn;
    public Button StopBtn;
    //代表地图边界的物体的TranSform组件，实际地图需要用他们加上半个格子大小得出

    public Transform maxPosx;
    public Transform minPosx;
    public Transform maxPosy;
    public Transform minPosy;
    private Image[,] mapData = new Image[7, 6];
    public Sprite[] sprites;//不同颜色的??
    private int score = 0;//分数
    public Text scoreText;//分数显示的文本
    private float spacing;//两条鱼之间的间距
    public AudioClip[] audioClips;//游戏需要用到的音频剪辑
    bool isEnableInput = false;//是否允许用户输入
    Vector2 currentIndex = new Vector2(-1, -1);//当前索引，实际上就是表示当前玩家对那个格子进行操作。
    private AudioSource audioSource;
    public Transform map;


    // Start is called before the first frame update
    void Start()
    {
        spacing = Mathf.Abs(GameObject.Find("Row/block").transform.position.x -
            GameObject.Find("Row/block (1)").transform.position.x);
        audioSource = transform.GetComponent<AudioSource>();
        StartBtn.onClick.AddListener(StartGame);
        StopBtn.onClick.AddListener(StopGame);
        InitMap();
    }

    //初始化地图 在程序开始运行时调用一次，在游戏结束时调用一次
    private void InitMap()
    {
        
        for (int i = 0; i < map.childCount; i++)
        {
            Transform row = map.GetChild(i);
            //如果i小于地图的子物体数量i++
            for (int j = 0; j < row.childCount; j++)
            {
                //如果j小于地图里面每一行的子物体数量j++
                //实际上就是要查找到所有的格子
                //重置地图数据
                mapData[i, j] = row.GetChild(j).GetChild(0).GetComponent<Image>();
                //重置格子位置               
                mapData[i, j].transform.localPosition = Vector3.zero;
                //重置格子透明度
                mapData[i, j].DOFade(1, 0f);
                //最后重置为初始的??
                mapData[i, j].sprite = maxPosx.GetComponent<Image>().sprite;
            }
        }
    }

    //开始游戏
    private void StartGame()
    {
        //如果游戏总是在停止游戏的状态，那么进入游戏
        if (gameState == GameState.Stop)
        {
            //播放一个开始游戏的音效
            PlayAudio(0);
            //设置游戏的状态
            gameState = GameState.Start;
            //重置分数和显示分数的文本
            score = 0;
            scoreText.text = "得分：0";
            //随机给地图数据里面的图像分配??
            foreach (var item in mapData)
            {
                item.sprite = sprites[UnityEngine.Random.Range(0, sprites.Length)];
            }
            //然后还需要刚进入游戏的时候，检测一下地图有没有三个同样的??链接在一起，如果有
            //需要进行消除
            Clear();
        }
    }
    // 在Update中处理用户的移动
    void Update()
    {
        if (gameState == GameState.Start)
        {
            if (isEnableInput)
            {
                //游戏为开始状态且允许玩家输入才能处理玩家的移动
                if (Input.GetMouseButtonDown(0))
                {
                    //当玩家按下鼠标左键的时候，我们要去判断他点击的地方是否在地图范围内
                    if (CheckMousePos(Input.mousePosition))
                    { //如果是的话要去更新他当前的索引，也就是他当前的操作的对象
                        //实际上公式就是（鼠标位置-地图最低的位置）、间距
                        int xIndex = (int)((Input.mousePosition.y - (minPosy.position.y - spacing / 2)) / spacing);
                        //实际上公式就是（鼠标位置-地图最左边的位置）、间距
                        int yIndex = (int)((Input.mousePosition.x - (minPosy.position.x - spacing / 2)) / spacing);
                        currentIndex = new Vector2(xIndex, yIndex);//更新下当前索引
                    }
                   

                }
                if (Input.GetMouseButton(0))
                {
                    //按住鼠标左键的期间
                    if (currentIndex.x != -1)
                    {//也就是说玩家当前有操作目标的时候

                        if (Input.mousePosition.x - mapData[(int)currentIndex.x,(int)currentIndex.y].transform.position.x > spacing / 2)
                        {//需要进行移动了 这里是针对X的位置的，而且是往右边移动
                         //往右边移动一个格子
                            AttemptMov(new Vector2(0, 1));
                        }
                        else if (Input.mousePosition.x - mapData[(int)currentIndex.x, (int)currentIndex.y].transform.position.x < -spacing / 2)
                        {
                            //往左边移动一个格子
                            AttemptMov(new Vector2(0, -1));
                        }
                        else if (Input.mousePosition.y - mapData[(int)currentIndex.x, (int)currentIndex.y].transform.position.y > spacing / 2)
                        {
                            //往上移动
                            AttemptMov(new Vector2(1, 0));
                        }
                        else if (Input.mousePosition.y - mapData[(int)currentIndex.x, (int)currentIndex.y].transform.position.y < -spacing / 2)
                        {
                            //往下移动
                            AttemptMov(new Vector2(-1, 0));
                        }
                    }
                }

                if (Input.GetMouseButtonUp(0))
                {
                    //如果用户放开鼠标重置当前索引
                    currentIndex.x = -1;
                }
            }

        }
    }

    //尝试移动，实际上就是先判断能否移动到目标位置，如果可以再调用移动的方法进行移动过去
    private void AttemptMov(Vector2 moveTarget)
    {
        if (currentIndex.x != -1)
        {
            if (moveTarget.x != 0)
            {//上下移动的时候
                if (currentIndex.x+ moveTarget.x>=0 && currentIndex.x + moveTarget.x < mapData.GetLength(0))
                {
                    //如果要移动的位置没有超出地图范围可以进行移动
                    Move(currentIndex, currentIndex + moveTarget, true);
                }
            }
            else
            {
                if (currentIndex.y + moveTarget.y >= 0 && currentIndex.y + moveTarget.y < mapData.GetLength(1))
                {
                    //如果要移动的位置没有超出地图范围可以进行移动
                    Move(currentIndex, currentIndex + moveTarget, true);

                }
            }
        }
    }

    //移动处理
    private void Move(Vector2 index1,Vector2 index2,bool isClear)
    {
        //播放移动的音效
        PlayAudio(1);
        isEnableInput = false;
        Image shape1 = mapData[(int)index1.x, (int)index1.y];
        Image shape2 = mapData[(int)index2.x, (int)index2.y];
        currentIndex.x = -1;
        //移动并更新地图信息
        shape1.transform.DOMove(shape2.transform.position, 0.2f);
        shape2.transform.DOMove(shape1.transform.position, 0.2f).OnComplete(() =>
        {
            mapData[(int)index1.x, (int)index1.y] = shape2;
            mapData[(int)index2.x, (int)index2.y] = shape1;
            //如果需要检测消除的话调用消除的方法并根据返回值决定是否要回到移动前的位置
            //玩家手动操作的移动需要检测消除，当判断不需要消除回到之前位置的时候就不需要检测
            if (isClear)
            {
                if (Clear() == false)
                {//移动回之前的位置
                    Move(index1, index2, false);
                }
            }
            else {
                isEnableInput = true;
            }
        });
    }


    //检测鼠标位置是否在地图范围内，参数是鼠标位置
    private bool CheckMousePos(Vector3 mousePos)
    {
        if (mousePos.x<maxPosx.position.x+spacing/2 && mousePos.x>minPosx.position.x - spacing/2)
        {//说明x在地图范围内
            if (mousePos.y < maxPosy.position.y + spacing / 2 && mousePos.y > minPosy.position.y - spacing / 2)
            {
                //Y在范围内
                return true;
            }
        }
        return false;
    }

    //消除处理，方法内部去对每个格子进行检测是否需要消除以及做消除处理
    //这里是每次玩家移动完之后调用，游戏开始的时候调用一次，消除完之后再调用一次。
    private bool Clear()
    {
        //处理消除期间禁止用户输入
        isEnableInput = false;
        //一个用来存放所有需要被消除的图像的List
        List<Image> clears = new List<Image>();

        for (int i = 0; i<mapData.GetLength(0);i++)
        {
            //取出每一行
            for (int j = 0; j < mapData.GetLength(1); j++)
            {
                //取出每一行里面的每一个格子
                //检测这个格子是否需要消除
                //传递的参数代表地图数据里面两个索引以及要被消除的图像的List
                CheckClear(i, j, clears);
            }
        }
        //检测完全地图数据之后，进行消除处理
        if (clears.Count > 0)
        {
            //每有一个格子被消除+50分
            score += clears.Count * 50;
            scoreText.text = "得分:" + score;

            for (int i = 0; i < clears.Count; i++)
            {
                //取出所有被消除的格子
                Image shape = clears[i];
                //播放消除的音效TODO
                PlayAudio(2);
                //进行消除处理
                if(i == clears.Count -1)
                {
                    //如果取的是最后一块，那么除了消除之外，还有更新地图最新的数据
                    shape.DOFade(0, 0.2f).SetLoops(4).SetEase(Ease.Linear)
                        .OnComplete(() =>
                        {
                            //最后一个消除完毕之后需要执行的事情，也就是更新地图数据
                            //被消除的方块还原为初始的??，也就是白色底图，以及恢复透明度
                            shape.sprite = maxPosx.GetComponent<Image>().sprite;
                            shape.DOFade(1, 0f);
                            UpdateMapData();
                        });
                }
                else
                {

                    //如果取得的不是最后一块，只需要执行消除的操作就可以了
                    shape.DOFade(0, 0.2f).SetLoops(4).SetEase(Ease.Linear)
                        .OnComplete(() =>
                        {

                            //被消除的方块还原为初始的??，也就是白色底图，以及恢复透明度
                            shape.sprite = maxPosx.GetComponent<Image>().sprite;
                            shape.DOFade(1, 0f);
                        });
                }
            }
            return true;//返回true代表需要消除

        }
        else
        {
            //重新允许用户输入，以及返回false
            isEnableInput = true;
            return false;
        }


    }
   

    //检测每个格子上方和右方相邻的格子是否需要进行消除，如果需要就添加到参数的
    //List里面，因为LIST是引用类型的，所以我们在这里添加了
    // <param name = "xIndex"></param>
    // <param name = "yIndex"></param>
    // <param name = "clears"></param>
    private void CheckClear(int xIndex, int yIndex, List<Image> clears)
    {
        //检测x的索引改变，y的索引不变，是否相同，也就是说同一列里面相邻的三个是否相同
        for (int i = xIndex + 1; i < mapData.GetLength(0); i++)
        {
            //通过对比🐟的名称，确定两个是否相同
            if (mapData[xIndex, yIndex].sprite.name == mapData[i, yIndex].sprite.name)
            {
                if (i - xIndex == 2)
                {//说明已经有三个相同的了，符合消除的条件
                    if (!clears.Contains(mapData[xIndex, yIndex]))
                    { //如果符合消除的条件，且List里面没有包含这个图像，那么添加到List里面
                        clears.Add(mapData[xIndex, yIndex]);
                    }

                    if (!clears.Contains(mapData[xIndex + 1, yIndex]))
                    { //如果符合消除的条件，且List里面没有包含这个图像，那么添加到List里面
                        clears.Add(mapData[xIndex + 1, yIndex]);
                    }

                    if (clears.Contains(mapData[xIndex + 2, yIndex]) == false)
                    { //如果符合消除的条件，且List里面没有包含这个图像，那么添加到List里面
                        clears.Add(mapData[xIndex + 2, yIndex]);
                    }
                }
                else if (i - xIndex > 2)
                {
                    if (!clears.Contains(mapData[i, yIndex]))
                    { //如果符合消除的条件，且List里面没有包含这个图像，那么添加到List里面
                        clears.Add(mapData[i, yIndex]);
                    }
                }
            }
            else
            {
                break;//有一块名称不同了，跳出循环，也就是跳出列不同行的检测
            }

        }
        //检测Y的索引改变，X的索引不变，是否相同，也就是说同一行里面相邻的三个是否相同
        for (int i = yIndex + 1; i < mapData.GetLength(1); i++)
        {
            //通过对比??的名称，确定两个是否相同
            if (mapData[xIndex, yIndex].sprite.name == mapData[xIndex, i].sprite.name)
            {
                if (i - yIndex == 2)
                {//说明已经有三个相同的了，符合消除的条件
                    if (!clears.Contains(mapData[xIndex, yIndex]))
                    { //如果符合消除的条件，且List里面没有包含这个图像，那么添加到List里面
                        clears.Add(mapData[xIndex, yIndex]);
                    }

                    if (!clears.Contains(mapData[xIndex, yIndex + 1]))
                    { //如果符合消除的条件，且List里面没有包含这个图像，那么添加到List里面
                        clears.Add(mapData[xIndex, yIndex + 1]);
                    }

                    if (!clears.Contains(mapData[xIndex, yIndex + 2]))
                    { //如果符合消除的条件，且List里面没有包含这个图像，那么添加到List里面
                        clears.Add(mapData[xIndex, yIndex + 2]);
                    }
                }
                else if (i - yIndex > 2)
                {
                    if (!clears.Contains(mapData[xIndex, i]))
                    { //如果符合消除的条件，且List里面没有包含这个图像，那么添加到List里面
                        clears.Add(mapData[xIndex, i]);
                    }
                }
            }
            else
            {
                break;//有一块名称不同了，跳出循环，也就是跳出列不同行的检测
            }
        }
    }

    //更新地图数据
    private void UpdateMapData()
    { 
        //用于记录需要变更位置的图像的字典
        //键是需要调整位置的图像，值是需要下降的格子数量
        Dictionary<Image, int> downData = new Dictionary<Image, int>();
        List<Image> clearShape = new List<Image>();//这一列被清除的图像
        int maxclearCount = 0;//最大的消除的数量，主要是帮我们确认要间隔多久后检测地图数据是否需要被消除
        //获取到每一列里面每个格子需要下降的格子数量，以及把被消除的格子移到地图外等待降落
        //获取到每一列
        for (int i = 0; i < mapData.GetLength(1); i++)
        {//清空每一列被清除的图像数据，避免上一列的数据影响到这一列
            clearShape.Clear();
            int clearCount = 0;//这一列消除的数量
            //获取到每一行
            for (int j = 0; j < mapData.GetLength(0); j++)
            {
                Image shape = mapData[j, i];//获取到每个格子
                if (shape.sprite.name == maxPosx.GetComponent<Image>().sprite.name)

                {
                    //因为maxPosx这个图像的🐟我们是不会去改变的，所以我们可以通过他来确定是否被消除
                    //如果格子内的🐟名称=最开始的🐟名称，说明被清除了
                    mapData[j, i] = null; //把被消除的地图数据设置为空
                    clearCount += 1;
                    //把他设置到地图外被消除的格子数量的位置上，等待降落
                    shape.transform.position += new Vector3(0, (mapData.GetLength(0) - j - 1 + clearCount) * spacing, 0);
                    //分配新的🐟
                    shape.sprite= sprites[UnityEngine.Random.Range(0, sprites.Length)];
                    //加入到新的List里面
                    clearShape.Add(shape);

                }   
                else if (clearCount!=0)
                {
                    mapData[j, i] = null;//如果这一列有消除过格子，需要把当前格子设置为空等待下降
                    //添加进等待下降的字典中
                    downData.Add(shape, clearCount);
                    mapData[j - clearCount, i] = shape;
                }

            }
            for (int k = 0; k < clearShape.Count; k++)
            {
                //添加要降落的格子数量
                downData.Add(clearShape[k], clearCount);
                //总的行数-这一列消除的数量=被消除的格子初始位置+k=实际应该在的位置
                int xIndex = 7 - clearCount + k;

                mapData[xIndex, i] = clearShape[k];
            }
           
            //如果这一列消除的数量超出了当前最大的消除数量，那么更新最大的消除数量
            if (clearCount > maxclearCount)
            {
                maxclearCount = clearCount;
            }
        }

        //开始降落
        foreach (var item in downData.Keys)
        {
            //获取到要下降到的目标位置
            Vector3 targetPos = item.transform.position + new Vector3(0, -spacing, 0) * downData[item];
           //进行降落，每降落一个格子用0.1秒的时间
            item.transform.DOMove(targetPos, 0.1f * downData[item]);
        }
        //是要等待全部降落完毕之后，重新检测最新的地图是否需要消除
        StartCoroutine(WaitClear(maxclearCount * 0.1f));
    }

    //每次消除并更新地图数据后，等待一定时间（所有格子位置调整完毕后）重新检测是否要消除
    private IEnumerator WaitClear(float time)
    {
        yield return new WaitForSeconds(time);
        //等待全部降落完毕
        if (gameState == GameState.Start)
        {//如果游戏还在运行的话，重新调用消除的方法
            Clear();
        }
    }

    //停止游戏
    private void StopGame()
    {
        if (gameState==GameState.Start)
        {
            gameState = GameState.Stop;
            //清楚DOTween的动画
            DOTween.Clear();
            score = 0;//把分数清空
            scoreText.text = "得分：0";
            //重新初始化地图
            InitMap();
        }
    }

    //播放音频，参数是要播放的音频剪辑的索引
    private void PlayAudio(int index)
    {
        audioSource.clip = audioClips[index];
        audioSource.Play();
    }
    
}
